mod api;
mod jwt;
mod res;
mod utils;

use axum::{
    routing::{delete, get, get_service, post},
    AddExtensionLayer, Router,
};
use dotenv::dotenv;
use hyper::{Method, StatusCode};
use sqlx::mysql::MySqlPoolOptions;
use std::env::var;
use std::net::SocketAddr;
use tower_http::{
    cors::{any, CorsLayer},
    services::ServeDir,
};

use crate::api::{
    cook::{check_cook, delete_cook, get_spare_time, list_cooks, set_spare_time, get_cook, get_food_types, get_cook_service},
    user::{get_users, phone_login},
};
use crate::api::{
    cook::{cook_me, create_cook},
    menus::{create_menu, delete_menu, edit_menu, get_menu_detail, get_menus},
    order::{create_order, delete_order, edit_order, get_orders},
    reservation::{create_reservation, delete_reservation, edit_reservation, get_reservations},
    user::{get_phone_code, me, phone_password_login},
};

#[tokio::main]
async fn main() {
    dotenv().ok();

    log4rs::init_file("log4rs.yaml", Default::default()).unwrap();

    let client = redis::Client::open(format!(
        "redis://{}:{}/{}",
        var("REDIS_HOST").expect("获取 REDIS_HOST 错误"),
        var("REDIS_PORT").unwrap_or("6379".to_owned()),
        var("REDIS_DB").unwrap_or("0".to_owned())
    ))
    .unwrap();

    // 数据库连接池
    let pool = MySqlPoolOptions::new()
        .max_connections(5)
        .connect(&var("DATABASE_URL").unwrap())
        .await
        .unwrap();

    let user = Router::new()
        .route("/", get(get_users))
        .route("/me", get(me));

    let menu = Router::new()
        .route("/", post(create_menu))
        .route("/", get(get_menus))
        .route("/:id", get(get_menu_detail))
        .route("/:id", post(edit_menu))
        .route("/:id", delete(delete_menu));

    let cook = Router::new()
        .route("/", post(create_cook))
        .route("/", get(list_cooks))
        .route("/food_types", get(get_food_types))
        .route("/service", get(get_cook_service))
        .route("/me", get(cook_me))
        .route("/:id/check", post(check_cook))
        .route("/:id", get(get_cook))
        .route("/:id", delete(delete_cook))
        .route("/:id/spare_time", post(set_spare_time))
        .route("/:id/spare_time", get(get_spare_time));

    let reservation = Router::new()
        .route("/", get(get_reservations))
        .route("/", post(create_reservation))
        .route("/:id", post(edit_reservation))
        .route("/:id", delete(delete_reservation));

    let order = Router::new()
        .route("/", get(get_orders))
        .route("/", post(create_order))
        .route("/:id/status", post(edit_order))
        .route("/:id", delete(delete_order));

    let api = Router::new()
        .nest("/users", user) // 用户接口
        .nest("/cooks", cook) // 厨师接口
        .nest("/menus", menu) // 菜单接口
        .nest("/reservations", reservation) // 预约接口
        .nest("/orders", order) // 订单接口
        .route("/login/phone/code", post(get_phone_code))
        .route("/login/phone", post(phone_login))
        .route("/login", post(phone_password_login))
        .route("/upload", post(api::upload::upload_file));

    let app = Router::new()
        .nest(
            "/static",
            get_service(ServeDir::new(var("UPLOAD_DIR").unwrap())).handle_error(|error: std::io::Error| async move {
                (
                    StatusCode::INTERNAL_SERVER_ERROR,
                    format!("Unhandled internal error: {}", error),
                )
            }),
        )
        .nest("/api", api)
        .route("/protected", get(jwt::protected))
        .route("/authorize", post(jwt::authorize))
        .layer(AddExtensionLayer::new(client))
        .layer(AddExtensionLayer::new(pool))
        .layer(
            CorsLayer::new()
                .allow_headers(any())
                .allow_origin(any())
                .allow_methods(any()),
        );

    let port: u16 = var("PORT").unwrap_or("8000".to_string()).parse().unwrap();

    let addr = SocketAddr::from(([127, 0, 0, 1], port));
    log::info!("listening on {}", addr);
    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
}
